package com.doge.walknovel.business.reader

import android.graphics.Typeface
import androidx.lifecycle.MutableLiveData
import androidx.paging.DataSource
import androidx.paging.PagedList
import androidx.paging.toLiveData
import com.doge.walknovel.api.*
import com.doge.walknovel.api.model.ChapterInfo
import com.doge.walknovel.api.model.TextInfo
import com.doge.walknovel.api.model.chapterPageSize
import com.doge.walknovel.business.home.profile.topup.TopUpActivity
import com.doge.walknovel.business.home.profile.topup.TopUpRefer
import com.doge.walknovel.business.reader.ReaderState.Companion.ID_CHAPTER_TITLE
import com.doge.walknovel.business.reader.ReaderState.Companion.ID_LOCKED
import com.doge.walknovel.business.reader.ReaderState.Companion.ID_READER_NEXT
import com.doge.walknovel.business.reader.ReaderState.Companion.ID_READER_PRE
import com.doge.walknovel.business.reader.download.ChapterTextCacheHelper
import com.doge.walknovel.core.base.LoadStatus
import com.doge.walknovel.core.base.adapters.*
import com.doge.walknovel.utils.*

class ReaderRepository(val novelId: Int) {
    private val s = "#"
    private var readerState: ReaderState = ReaderState()
    private var readerDataSource: XPagedKeyDataSource? = null
    val dataList = readerDataSource().toLiveData(
        config = PagedList.Config.Builder().setPrefetchDistance(200).build(),
        boundaryCallback = object : PagedList.BoundaryCallback<Cell>() {
            override fun onItemAtEndLoaded(itemAtEnd: Cell) {
                if (itemAtEnd.id.startsWith(ID_READER_NEXT)) {
                    redirectToChapterId(itemAtEnd.extra as? Int?, Direction.NEXT)
                }
            }

            override fun onItemAtFrontLoaded(itemAtFront: Cell) {
                if (itemAtFront.id.startsWith(ID_READER_PRE)) {
                    redirectToChapterId(itemAtFront.extra as? Int?, Direction.PRE)
                }
            }
        })

    private fun process(data: ChapterInfo?, direction: Direction) {
        if (data == null) return

        if (direction == Direction.PRE && readerState.preChapterId == data.chapterId) {
            log("ReaderRepository process Direction.PRE currentPre(${readerState.preChapterId}) processData(${data.chapterId})")
            // insert pre data
            val list = readerState.list.toMutableList()
            val texts = readerState.texts.toMutableMap()

            val p: String? = list.find { it.startsWith(ID_READER_PRE) }
            p?.let { list.remove(it) }

            if (data.locked) {
                list.add(0, "$ID_LOCKED${data.chapterId}")
            } else {
                // list.remove("$ID_LOCKED${data.chapterId}")
                ChapterTextCacheHelper.requireChapterText(novelId, data).reversed().forEach {
                    val key = "${data.chapterId}${s}${it.textId}"
                    texts[key] = it
                    list.add(0, key)
                }
                list.add(0, "$ID_CHAPTER_TITLE${data.chapterId}")
            }

            if (!data.preChapterId.isNullOrZero()) list.add(
                0,
                "${ID_READER_PRE}${data.preChapterId}"
            )

            readerState = readerState.copy(
                list = list, texts = texts,
                chapters = readerState.chapters.appendData(listOf(data)) { it.chapterId.toString() },
                preChapterId = data.preChapterId,
            )
        } else if (direction == Direction.NEXT && readerState.nextChapterId == data.chapterId) {
            log("ReaderRepository process Direction.NEXT currentNext(${readerState.nextChapterId}) processData(${data.chapterId})")
            // insert next data
            val list = readerState.list.toMutableList()
            val texts = readerState.texts.toMutableMap()

            val n: String? = list.find { it.startsWith(ID_READER_NEXT) }
            n?.let { list.remove(it) }

            if (data.locked) {
                list.add("$ID_LOCKED${data.chapterId}")
            } else {
                // list.remove("$ID_LOCKED${data.chapterId}")
                list.add("$ID_CHAPTER_TITLE${data.chapterId}")
                ChapterTextCacheHelper.requireChapterText(novelId, data).forEach {
                    val key = "${data.chapterId}${s}${it.textId}"
                    texts[key] = it
                    list.add(key)
                }
            }

            if (!data.nextChapterId.isNullOrZero()) list.add("${ID_READER_NEXT}${data.nextChapterId}")

            readerState = readerState.copy(
                list = list, texts = texts,
                chapters = readerState.chapters.appendData(listOf(data)) { it.chapterId.toString() },
                nextChapterId = data.nextChapterId,
            )
        } else if (direction == Direction.BOTH) {
            log("ReaderRepository process Direction.BOTH processData(${data.chapterId})")
            // first data or reset data 跳章
            val list: MutableList<String> = mutableListOf()
            val texts: MutableMap<String, TextInfo> = mutableMapOf()

            if (!data.preChapterId.isNullOrZero()) list.add("${ID_READER_PRE}${data.preChapterId}")

            if (data.locked) {
                list.add("$ID_LOCKED${data.chapterId}")
            } else {
                // list.remove("$ID_LOCKED${data.chapterId}")
                list.add("$ID_CHAPTER_TITLE${data.chapterId}")
                ChapterTextCacheHelper.requireChapterText(novelId, data).forEach {
                    val key = "${data.chapterId}${s}${it.textId}"
                    texts[key] = it
                    list.add(key)
                }
            }

            if (!data.nextChapterId.isNullOrZero()) list.add("${ID_READER_NEXT}${data.nextChapterId}")

            readerState = readerState.copy(
                list = list, texts = texts,
                chapters = mapOf<String, ChapterInfo>().appendData(listOf(data)) { it.chapterId.toString() },
                nextChapterId = data.nextChapterId,
                preChapterId = data.preChapterId
            )
        } else if (direction == Direction.UNLOCK) {
            log("ReaderRepository process Direction.UNLOCK processData(${data.chapterId})")

            val list = readerState.list.toMutableList()
            val texts = readerState.texts.toMutableMap()

            val lockedIdx = list.indexOf("$ID_LOCKED${data.chapterId}")
            if (lockedIdx >= 0) {
                ChapterTextCacheHelper.requireChapterText(novelId, data).reversed().forEach {
                    val key = "${data.chapterId}${s}${it.textId}"
                    texts[key] = it
                    list.add(lockedIdx + 1, key)
                }
                list.add(lockedIdx + 1, "$ID_CHAPTER_TITLE${data.chapterId}")
            }
            // list.remove("$ID_LOCKED${data.chapterId}")

            readerState = readerState.copy(
                list = list, texts = texts,
                chapters = readerState.chapters.appendData(listOf(data)) { it.chapterId.toString() },
            )
        }
        readerDataSource?.invalidate()
    }

    private fun processUnLocking(chapterId: Int, loadStatus: LoadStatus) = runOnWork {
        readerState = readerState.copy(
            chapters = readerState.chapters.replace {
                var result: ChapterInfo? = null
                if (it.chapterId == chapterId) result = it.copy(appUnlocking = loadStatus)
                result
            }
        )
        readerDataSource?.invalidate()
    }

    fun changeSize(value: Int) {
        require(!isMainThread())
        readerState = readerState.copy(
            texts = readerState.texts.replace {
                it.copy(size = value)
            },
            chapters = readerState.chapters.replace {
                it.copy(appUIUpdateFlag = "size:$value") // size changed
            }
        )
        readerDataSource?.invalidate()
    }

    fun changeLight(light: Boolean) {
        require(!isMainThread())
        readerState = readerState.copy(
            texts = readerState.texts.replace {
                it.copy(color = if (light) "#333333" else "#999999")
            },
            chapters = readerState.chapters.replace {
                it.copy(appUIUpdateFlag = "light:$light") // light changed
            }
        )
        readerDataSource?.invalidate()
    }

    fun changeBgColor(bgColor: String) {
        require(!isMainThread())
        readerState = readerState.copy(
            chapters = readerState.chapters.replace {
                it.copy(appUIUpdateFlag = "bgColor:$bgColor") // bgColor changed
            }
        )
        readerDataSource?.invalidate()
    }

    fun changeTypeface(typeface: Typeface) {
        require(!isMainThread())
        readerState = readerState.copy(
            texts = readerState.texts.replace {
                it.copy(typeface = typeface)
            },
            chapters = readerState.chapters.replace {
                it.copy(appUIUpdateFlag = "typeface:$typeface") // typeface changed
            }
        )
        readerDataSource?.invalidate()
    }

    private fun readerDataSource(): DataSource.Factory<Int, Cell> =
        object : DataSource.Factory<Int, Cell>() {
            override fun create(): DataSource<Int, Cell> {
                return object : XPagedKeyDataSource() {
                    override fun totalCount(): Int {
                        return readerState.list.size
                    }

                    override fun loadRange(start: Int, count: Int): List<Cell?> {
                        return readerState.list.mapToCell(start, count) {
                            when {
                                it.startsWith(ID_READER_PRE) -> Cell(
                                    ItemType.PreLoading,
                                    it, extra = readerState.preChapterId
                                )
                                it.startsWith(ID_READER_NEXT) -> Cell(
                                    com.doge.walknovel.core.base.adapters.ItemType.LoadingPlaceHolder,
                                    it, extra = readerState.nextChapterId
                                )
                                it.startsWith(ID_LOCKED) -> Cell(
                                    ItemType.Locked,
                                    it.substring(ID_LOCKED.length),
                                    extra = readerState.chapters[it.substring(ID_LOCKED.length)]
                                )
                                it.startsWith(ID_CHAPTER_TITLE) -> Cell(
                                    ItemType.ChaptersTitle,
                                    it.substring(ID_CHAPTER_TITLE.length),
                                    extra = readerState.chapters[it.substring(ID_CHAPTER_TITLE.length)]
                                )
                                it.contains(s) -> Cell(
                                    ItemType.Text,
                                    it,
                                    extra = readerState.texts[it]
                                )
                                else -> Cell(
                                    com.doge.walknovel.core.base.adapters.ItemType.Unknown,
                                    ""
                                )
                            }
                        }
                    }
                }.also { readerDataSource = it }
            }
        }

    fun requestUnlock(chapter: ChapterInfo?) {
        if (chapter == null) return
        processUnLocking(chapter.chapterId, LoadStatus.LOADING)
        api
            .novelChapterUnlock(chapter.chapterId)
            .map(ApiResultMapTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<ChapterInfo>() {
                override fun onNext(t: ChapterInfo) {
                    process(t, Direction.UNLOCK)
                    reduceChapterUnLock(t)
                }

                override fun onError(e: Throwable) {
                    super.onError(e)
                    processUnLocking(chapter.chapterId, LoadStatus.FAILURE)

                    if (e is ServerException && e.code == ErrorHandler.HTTP_NO_MONEY) {
                        runOnUI { TopUpActivity.start(Activities.top, TopUpRefer.unlockChapter) }
                    }
                }
            })
    }

    fun redirectToChapterId(chapterId: Int?, direction: Direction) {
        if (chapterId.isNullOrZero()) return
        ChapterCacheHelper.requireChapterInfo(chapterId!!) {
            runOnWork { process(it, direction) }
        }
    }

    fun redirectToChapterProgress(progress: Float?) {
        if (progress == null) return
        api
            .novelChapterInfoByProgress(novelId, progress * 100) // 后台精度0-100
            .map(ApiResultMapTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<ChapterInfo>() {
                override fun onNext(t: ChapterInfo) {
                    process(t, Direction.BOTH)
                }
            })
    }

    /////////////////////////////////////////////////////////

    val chapterLoading = MutableLiveData<LoadStatus>()
    private var chapterState: ListState<ChapterInfo> = ListState()
    private var chapterDataSource: XPagedKeyDataSource? = null

    fun requestChapters(initial: Boolean) {
        require(isMainThread())
        if (initial) chapterLoading.value = LoadStatus.LOADING
        val nextPage = if (initial) PAGE_INITIAL else (chapterState.page + 1)
        api
            .novelChapterList(novelId, nextPage)
            .map(ApiResultMapTransformer())
            .subscribeOnIO()
            .observeOnWork()
            .subscribe(object : ObserverAdapter<List<ChapterInfo>>() {
                override fun onNext(t: List<ChapterInfo>) {
                    reduceChapterOnNext(initial, t)
                }

                override fun onError(e: Throwable) {
                    reduceChapterOnError(initial)
                }
            })
    }

    private fun reduceChapterOnNext(initial: Boolean, data: List<ChapterInfo>) {
        require(!isMainThread())
        chapterState = chapterState.reduceOnNext(initial, data, chapterPageSize) {
            it.chapterId.toString()
        }
        chapterDataSource?.invalidate()
        runOnUI { chapterLoading.value = LoadStatus.SUCCESS }
    }

    private fun reduceChapterOnError(initial: Boolean) {
        require(!isMainThread())
        chapterState = chapterState.reduceOnError(initial)
        chapterDataSource?.invalidate()
        runOnUI { chapterLoading.value = LoadStatus.FAILURE }
    }

    private fun reduceChapterUnLock(t: ChapterInfo) {
        require(!isMainThread())
        chapterState = chapterState.copy(data = chapterState.data.replace {
            var result: ChapterInfo? = null
            if (it.chapterId == t.chapterId) result = it.copy(priceStatus = 2)
            result
        })
        chapterDataSource?.invalidate()
    }

    fun chapterDataSource(): DataSource.Factory<Int, Cell> =
        object : DataSource.Factory<Int, Cell>() {
            override fun create(): DataSource<Int, Cell> {
                return object : XPagedKeyDataSource() {
                    override fun totalCount(): Int {
                        return chapterState.list.size
                    }

                    override fun loadRange(start: Int, count: Int): List<Cell?> {
                        return chapterState.list.mapToCell(start, count) {
                            Cell(ItemType.Chapter, it, extra = chapterState.data[it])
                        }
                    }
                }.also { chapterDataSource = it }
            }
        }
}
